package com.hzw.saas.web.app.product.controller;

import java.io.IOException;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import javax.annotation.PostConstruct;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import com.alipay.api.AlipayApiException;
import com.hzw.saas.api.product.IPayService;
import com.hzw.saas.api.product.dto.BalanceOrderSuccessDto;
import com.hzw.saas.api.product.dto.ItemOrderSuccessDto;
import com.hzw.saas.api.product.dto.OrderStatusDto;
import com.hzw.saas.api.product.enums.OrderType;
import com.hzw.saas.common.config.configuration.properties.SaasHostProperties;
import com.hzw.saas.common.pay.alipay.AliTradeExecutor;
import com.hzw.saas.common.pay.alipay.AliTradeUtil;
import com.hzw.saas.common.pay.alipay.bean.AliQueryReqContent;
import com.hzw.saas.common.pay.bean.TradeStatus;
import com.hzw.saas.common.pay.enums.PayType;
import com.hzw.saas.common.pay.exception.TradeException;
import com.hzw.saas.common.pay.unionpay.UnionPayExecutor;
import com.hzw.saas.common.pay.unionpay.bean.UnionQueryReqContent;
import com.hzw.saas.common.pay.unionpay.sdk.AcpService;
import com.hzw.saas.common.pay.unionpay.sdk.SDKConstants;
import com.hzw.saas.common.util.PathUtils;
import cn.hutool.core.util.StrUtil;
import cn.hutool.core.util.URLUtil;
import cn.hutool.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import springfox.documentation.annotations.ApiIgnore;

/**
 * 支付回调接口
 *
 * @author zzl
 * @since 01/26/2021
 */
// @Controller
// @RequestMapping("/notice/pay")
@RequiredArgsConstructor
@Slf4j
@ApiIgnore
public class PayNoticeController {

    private final SaasHostProperties saasHostProperties;
    private final IPayService payService;

    /**
     * 购买支付成功，前台重定向地址
     */
    private String itemSuccessRedirectUrl;
    /**
     * 充值支付成功，前台重定向地址
     */
    private String balanceSuccessRedirectUrl;
    /**
     * 支付失败，前台重定向地址
     */
    private String failedRedirectUrl;

    /**
     * 支付结果未知，前台重定向地址
     */
    private String unknownRedirectUrl;


    @PostConstruct
    public void init() {
        String webHost = saasHostProperties.getAppFrontendHost();
        if (StrUtil.isBlank(webHost)) {
            log.error("前端服务Host配置为空");
        }
        // TODO: ZZL@01/29/2021 确定重定向地址
        this.itemSuccessRedirectUrl = PathUtils.connectUri(webHost, "#/pay-center/success?itemName={itemName}&expireDate={expireDate}");
        this.balanceSuccessRedirectUrl = PathUtils.connectUri(webHost, "#/pay-center/pay-recharge-success?balance={balance}");
        this.failedRedirectUrl = PathUtils.connectUri(webHost, "");
        this.unknownRedirectUrl = PathUtils.connectUri(webHost, "");
        log.debug("购买支付成功前台重定向地址：{}", this.itemSuccessRedirectUrl);
        log.debug("充值支付成功前台重定向地址：{}", this.balanceSuccessRedirectUrl);
        log.debug("支付失败前台重定向地址：{}", this.failedRedirectUrl);
        log.debug("支付结果未知前台重定向地址：{}", this.unknownRedirectUrl);
    }

    /**
     * 支付宝同步回调
     *
     * @param request
     * @return
     */
    @GetMapping("/ali/return")
    public void aliPayReturn(HttpServletRequest request, HttpServletResponse response) {

        Map<String, String> params = AliTradeUtil.convert(request.getParameterMap());
        log.debug("alipay return request: ");
        log.debug(params.toString());

        try {
            // 内部流水,即payNo
            String out_trade_no = request.getParameter("out_trade_no");
            // 外部流水
            String trade_no = request.getParameter("trade_no");
            if (!AliTradeUtil.notifyRSACheck(params)) {
                log.error("aliPayReturn：支付宝同步回调验签失败，payNo: {}", out_trade_no);
                response.sendRedirect(failedRedirectUrl);
                return;
            }
            log.debug("aliPayReturn：支付宝同步回调验签成功，payNo: {}", out_trade_no);
            AliQueryReqContent content = AliQueryReqContent.build()
                .out_trade_no(out_trade_no)
                .trade_no(trade_no);
            TradeStatus tradeStatus = AliTradeExecutor.query(content);

            switch (tradeStatus.getTradeStatus()) {
                case TRADE_SUCCESS:
                case TRADE_FINISHED:
                    log.debug("aliPayReturn：支付成功，payNo: {}", out_trade_no);
                    OrderStatusDto orderStatusDto = payService.pay(tradeStatus.getPayNo(), tradeStatus.getBizPayNo(), PayType.ALI_PAY.value(), null);
                    response.sendRedirect(getRedirectUrl(orderStatusDto));
                    break;
                case TRADE_CLOSED:
                    log.debug("aliPayReturn：支付关闭，payNo: {}", out_trade_no);
                    // TODO: ZZL@2020-11-04  超时处理
                    response.sendRedirect(unknownRedirectUrl);
                    break;
            }
        } catch (AlipayApiException | IOException e) {
            log.error("aliPayReturn error", e);
            try {
                response.sendRedirect(failedRedirectUrl);
            } catch (IOException ioException) {
                ioException.printStackTrace();
            }
        }
    }

    /**
     * 支付宝异步通知接口
     *
     * @param request
     */
    @PostMapping("/ali/notify")
    public void alipayNotify(HttpServletRequest request) {
        Map<String, String> params = AliTradeUtil.convert(request.getParameterMap());
        log.debug("alipay notify request: ");
        log.debug(params.toString());

        try {
            // 内部流水
            String out_trade_no = request.getParameter("out_trade_no");
            // 外部流水
            String trade_no = request.getParameter("trade_no");

            if (!AliTradeUtil.notifyRSACheck(params)) {
                //验签失败
                log.error("AlipayNotify：支付宝异步回调验签成功，payNo: {}", out_trade_no);
                return;
            }
            log.debug("AlipayNotify：支付宝异步回调验签成功，payNo: {}", out_trade_no);
            AliQueryReqContent content = AliQueryReqContent.build()
                .out_trade_no(out_trade_no)
                .trade_no(trade_no);
            TradeStatus tradeStatus = AliTradeExecutor.query(content);
            switch (tradeStatus.getTradeStatus()) {
                case TRADE_SUCCESS:
                case TRADE_FINISHED:
                    log.debug("AlipayNotify：支付成功，payNo: {}", out_trade_no);
                    payService.pay(tradeStatus.getPayNo(), tradeStatus.getBizPayNo(), PayType.ALI_PAY.value(), null);
                    break;
                case TRADE_CLOSED:
                    log.debug("AlipayNotify：支付关闭，payNo: {}", out_trade_no);
                    // TODO: ZZL@2020-11-04  超时处理
                    break;
            }
        } catch (AlipayApiException e) {
            log.error("AlipayNotify error", e);
        }
    }

    /**
     * 银联同步通知接口
     *
     * @param request
     * @return
     */
    @PostMapping("/union/return")
    public void unionPayReturn(HttpServletRequest request, HttpServletResponse response) {
        String encoding = request.getParameter(SDKConstants.param_encoding);
        // 获取银联通知服务器发送的后台通知参数
        Map<String, String> reqParam = getAllRequestParam(request);
        log.debug("union return request: ");
        log.debug(reqParam.toString());

        try {
            // 验签
            String payNo = reqParam.get("orderId");
            if (!AcpService.validate(reqParam, encoding)) {
                //验签失败
                log.error("银联同步回调验签失败, payNo: {}", payNo);
                response.sendRedirect(failedRedirectUrl);
                return;
            }
            log.debug("银联同步回调验签成功, payNo: {}", payNo);
            // 校验返回码
            String respCode = reqParam.get("respCode");
            //判断respCode=00、A6后，对涉及资金类的交易，请再发起查询接口查询，确定交易成功后更新数据库。
            if (!"00".equals(respCode) && !"A6".equals(respCode)) {
                log.debug("unionPayReturn: 支付结果未知, payNo: {}", payNo);
                response.sendRedirect(unknownRedirectUrl);
                return;
            }

            // 异步响应成功时再主动查询一次结果
            UnionQueryReqContent content = new UnionQueryReqContent();
            content.setOrderId(reqParam.get("orderId"));
            content.setTxnTime(reqParam.get("txnTime"));
            TradeStatus tradeStatus = UnionPayExecutor.query(content);

            switch (tradeStatus.getTradeStatus()) {
                case TRADE_SUCCESS:
                case TRADE_FINISHED:
                    log.debug("unionPayReturn: 支付成功, payNo: {}", payNo);
                    OrderStatusDto orderStatusDto = payService.pay(tradeStatus.getPayNo(), tradeStatus.getBizPayNo(), PayType.UNION_PAY.value(), null);
                    response.sendRedirect(getRedirectUrl(orderStatusDto));
                    break;
                case TRADE_CLOSED:
                    // TODO: ZZL@2020-11-04  超时处理
                    log.debug("unionPayReturn: 支付关闭, payNo: {}", payNo);
                    response.sendRedirect(unknownRedirectUrl);
                    break;
            }
        } catch (TradeException | IOException e) {
            log.error("unionPayReturn error", e);
            try {
                response.sendRedirect(failedRedirectUrl);
            } catch (IOException ioException) {
                ioException.printStackTrace();
            }
        }
    }

    /**
     * 银联异步通知接口
     *
     * @param request
     * @return
     */
    @PostMapping("/union/notify")
    public ResponseEntity<String> unionPayNotify(HttpServletRequest request) {
        String encoding = request.getParameter(SDKConstants.param_encoding);
        // 获取银联通知服务器发送的后台通知参数
        Map<String, String> reqParam = getAllRequestParam(request);
        log.debug("union notify request: ");
        log.debug(reqParam.toString());

        try {
            String payNo = reqParam.get("orderId");
            if (!AcpService.validate(reqParam, encoding)) {
                //验签失败
                log.error("银联异步回调验签失败, payNo: {}", payNo);
                return ResponseEntity.status(HttpStatus.HTTP_INTERNAL_ERROR).build();
            }
            log.debug("银联异步回调验签成功, payNo: {}", payNo);
            String respCode = reqParam.get("respCode");
            //判断respCode=00、A6后，对涉及资金类的交易，请再发起查询接口查询，确定交易成功后更新数据库。
            if (!"00".equals(respCode) && !"A6".equals(respCode)) {
                log.debug("unionPayNotify: 支付结果未知, payNo: {}", payNo);
                return ResponseEntity.status(HttpStatus.HTTP_INTERNAL_ERROR).build();
            }

            // 异步响应成功时再主动查询一次结果
            UnionQueryReqContent content = new UnionQueryReqContent();
            content.setOrderId(reqParam.get("orderId"));
            content.setTxnTime(reqParam.get("txnTime"));
            TradeStatus tradeStatus = UnionPayExecutor.query(content);
            switch (tradeStatus.getTradeStatus()) {
                case TRADE_SUCCESS:
                case TRADE_FINISHED:
                    log.debug("unionPayNotify: 支付成功, payNo: {}", payNo);
                    payService.pay(tradeStatus.getPayNo(), tradeStatus.getBizPayNo(), PayType.UNION_PAY.value(), null);
                    break;
                case TRADE_CLOSED:
                    log.debug("unionPayNotify: 支付关闭, payNo: {}", payNo);
                    // TODO: ZZL@2020-11-04  超时处理
                    break;
            }

        } catch (TradeException e) {
            log.error("unionPayNotify error", e);
        }
        //返回给银联服务器http 200  状态码
        return ResponseEntity.ok().build();
    }

    /**
     * 获取请求参数中所有的信息
     * 当商户上送frontUrl或backUrl地址中带有参数信息的时候，
     * 这种方式会将url地址中的参数读到map中，会导多出来这些信息从而致验签失败，这个时候可以自行修改过滤掉url中的参数或者使用getAllRequestParamStream方法。
     *
     * @param request
     * @return
     */
    public static Map<String, String> getAllRequestParam(final HttpServletRequest request) {
        Map<String, String> res = new HashMap<String, String>();
        Enumeration<?> temp = request.getParameterNames();
        if (null != temp) {
            while (temp.hasMoreElements()) {
                String en = (String) temp.nextElement();
                String value = request.getParameter(en);
                res.put(en, value);
                // 在报文上送时，如果字段的值为空，则不上送 (下面的处理为在获取所有参数数据时，判断若值为空，则删除这个字段)
                if (res.get(en) == null || "".equals(res.get(en))) {
                    res.remove(en);
                }
            }
        }
        return res;
    }

    private String getRedirectUrl(OrderStatusDto orderStatusDto) {
        String redirectUrl = "";
        if (Objects.equals(orderStatusDto.getOrderType(), OrderType.ITEM.value())) {
            ItemOrderSuccessDto itemOrder = orderStatusDto.getItemOrder();
            if (itemOrder != null) {
                redirectUrl = itemSuccessRedirectUrl.replace("{itemName}", URLUtil.encode(itemOrder.getItemName()))
                    .replace("{expireDate}", String.valueOf(itemOrder.getExpireDate().getTime()));
            } else {
                log.error("获取支付成功跳转链接异常，无法获取购买订单信息, orderId: " + orderStatusDto.getOrderId());
                redirectUrl = itemSuccessRedirectUrl;
            }

        } else if (Objects.equals(orderStatusDto.getOrderType(), OrderType.BALANCE.value())) {
            BalanceOrderSuccessDto balanceOrder = orderStatusDto.getBalanceOrder();
            if (balanceOrder != null) {
                redirectUrl = balanceSuccessRedirectUrl.replace("{balance}", String.valueOf(balanceOrder.getBalance()));
            } else {
                log.error("获取支付成功跳转链接异常，无法获取充值订单信息, orderId: " + orderStatusDto.getOrderId());
                redirectUrl = balanceSuccessRedirectUrl;
            }

        }
        log.debug("支付完成前台重定向：{}", redirectUrl);
        return redirectUrl;
    }

}
